// auth.js
// 功能：提供认证中间件，用于拦截需要登录权限的请求，验证用户身份合法性
// 作用：保护需要登录才能访问的接口，防止未授权访问

// 导入工具函数和数据模型
// verifyToken：自定义的JWT令牌验证函数（用于校验令牌有效性、解析令牌内容）
const { verifyToken } = require("../utils/jwt");
// User：用户数据模型（用于从数据库查询用户信息）
const { User } = require("../models");

/**
 * 保护路由中间件（需要登录才能访问的接口必须经过此中间件验证）
 * @param {Object} req - Express请求对象（包含客户端发送的信息）
 * @param {Object} res - Express响应对象（用于向客户端返回响应）
 * @param {Function} next - Express的next函数（调用后进入下一个中间件/控制器）
 */
const protect = async (req, res, next) => {
  try {
    // 声明变量用于存储从请求头中提取的JWT令牌
    let token;

    // 从请求头中提取令牌
    // 前端需按规范在请求头中携带令牌：键为Authorization，值为"Bearer 令牌字符串"
    if (
      // 检查请求头中是否存在Authorization字段
      // 检查Authorization值是否以"Bearer "开头（规范格式）
      req.headers.authorization &&
      req.headers.authorization.startsWith("Bearer")
    ) {
      // 拆分字符串提取令牌：例如"Bearer abc123" → 拆分后数组为["Bearer", "abc123"]，取索引1的值
      token = req.headers.authorization.split(" ")[1];
    }

    // 若未提取到令牌（前端未传递或格式错误）
    if (!token) {
      // 返回401状态码（未授权）和错误信息，终止后续逻辑
      return res.status(401).json({
        message: "未授权，未提供有效的令牌",
      });
    }

    // 验证令牌有效性(验证失败会直接抛出错误，进入catch)
    // decoded为解析后的令牌内容（通常包含用户ID等关键信息）
    const decoded = verifyToken(token);

    // 令牌有效 → 根据令牌中的用户ID查询数据库，获取用户信息
    // findByPk：根据主键（ID）查询单条记录的ORM方法
    // attributes: { exclude: ["password"] }：查询结果排除password字段（安全考虑，避免密码泄露）
    const user = await User.findByPk(decoded.id, {
      attributes: { exclude: ["password"] },
    });

    // 若查询不到用户（可能用户已被删除，但令牌未过期）
    if (!user) {
      return res.status(401).json({ message: "未获授权，用户不存在" });
    }

    // 将查询到的用户信息挂载到req对象上
    // 后续的中间件或控制器可通过req.user直接获取当前登录用户的信息（无需重复查询）
    req.user = user;

    // 调用next()，让请求进入下一个处理环节（如业务逻辑控制器）
    next();
  } catch (error) {
    // 区分令牌相关错误和其他错误
    if (
      error.name === "TokenExpiredError" ||
      error.name === "JsonWebTokenError"
    ) {
      return res.status(401).json({ message: error.message });
    }
    // 捕获整个过程中可能发生的异常（如数据库查询失败、verifyToken抛出的错误等）
    // 将错误传递给Express的全局错误处理中间件处理
    next(error);
  }
};

// 导出中间件，供路由模块导入使用
module.exports = {
  protect,
};
